package com.googlecode.afx.utils;

import java.io.IOException;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationUtils;

import com.googlecode.afx.annotation.AFXView;
import com.googlecode.afx.view.NodeWrapper;

import javafx.scene.Node;
import javafx.scene.control.Labeled;
import javafx.scene.control.ProgressIndicator;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;

/**
 * General utility class for working with JavaFX.
 * 
 * @author MartinKoster
 *
 */
public class AFXUtils {

	private static final Log LOG = LogFactory.getLog(AFXUtils.class);
	
	/**
	 * Finds a node in the scene graph represented by <tt>node</tt> that has the given <tt>id</tt>.
	 * 
	 * @param node
	 * @param id
	 * @return
	 */
	public static Node findNodeById(Node node, String id) {
		FindByNodeIdHandler handler = new FindByNodeIdHandler(id);
		NodeWrapper wrapper = new NodeWrapper(node);
		wrapper.traverse(handler);
		if(handler.hasFound()) {
			return handler.getResult();
		} 
		return null;
	}

	/**
	 * Sets the text property of a node identified by <tt>id</tt>.
	 * 
	 * @param id
	 * @param text
	 */
	public static void setLabelText(String id, String text, Node root) {
		Node node = findNodeById(root, id);
		if(node != null && node instanceof Labeled) {
			Labeled labeled = (Labeled) node;
			labeled.setText(text);
		}
	}
	
	/**
	 * Sets a progress value of a node identified by <tt>id</tt>.
	 *  
	 * @param id
	 * @param value
	 * @param root
	 */
	public static void setProgressValue(String id, double value, Node root) {
		Node node = findNodeById(root, id);
		if(node != null && node instanceof ProgressIndicator) {
			ProgressIndicator progressIndicator = (ProgressIndicator) node;
			progressIndicator.setProgress(value);
		}
	}

	/**
	 * Sets a progress indicator identified by <tt>id</tt> to indeterminate or not. 
	 * 
	 * @param id
	 * @param indeterminate
	 * @param root
	 */
	public static void setProgressIndeterminate(String id, boolean indeterminate, Node root) {
		Node node = findNodeById(root, id);
		if(node != null) {
			
		}		
	}
	
	/**
	 * Hides a node identified by <tt>id</tt>.
	 * 
	 * @param id
	 * @param root
	 */
	public static void hideNode(String id, Node root) {
		Node node = findNodeById(root, id);
		if(node != null) {
			node.setVisible(false);
		}
	}
	
	/**
	 * Displays a node identified by <tt>id</tt>.
	 * 
	 * @param id
	 * @param root
	 */
	public static void displayNode(String id, Node root) {
		Node node = findNodeById(root, id);
		if(node != null) {
			node.setVisible(true);
		}
	}
	
	/**
	 * Loads an FXML file and returns its root node.
	 * 
	 * @param fxmlFile
	 * @return
	 */
	public static Node loadFxml(String fxmlFile) {
		
		try {
			return FXMLLoader.load(AFXUtils.class.getResource(fxmlFile));
		}
		catch(IOException e) {
			throw new RuntimeException("Can not load file '" + fxmlFile + "'", e);
		}
	}
	
	/**
	 * Determines the view ID for the given controller class <tt>controller</tt>.
	 * 
	 * @param controller
	 * @return
	 */
	public static String determineViewIdForController(Class<?> controller) {
		if(controller == null) {
			return null;
		}
		// extract AFXView annotation
		AFXView jfxView = AnnotationUtils.findAnnotation(controller, AFXView.class);
		if(jfxView == null) {
			return null;
		} else {
			return jfxView.id();
		}
	}	
	
	/**
	 * Checks, if the given <tt>value</tt> is <tt>null</tt> or empty.
	 *
	 * @param value the value to check
	 * @return
	 */
	public static boolean isEmpty(Object value) {
		if(value == null) {
			return true;
		}
		if(value instanceof String) {
			return StringUtils.trimToNull((String) value) == null;
		}
		if(value instanceof StringProperty) {
			return StringUtils.trimToNull(((StringProperty) value).get()) == null;
		}
		if(value instanceof ObservableValue<?>) {
			return ((ObservableValue<?>) value).getValue() == null;
		}
		return false;
	}
	
	/**
	 * Wrapper for <tt>Runnable</tt> that does not swallow exceptions.
	 * 
	 * @param r
	 * @return
	 */
	public static Runnable getWrapper(final Runnable r) {
	    return new Runnable() {

	        @Override
	        public void run() {
	            try {
	                r.run();
	            } catch (Exception e) {
	            	LOG.error(e.getMessage(), e);
	            	throw e;
	            }
	        }
	    };
	}	
}
